"""
@breif (简单的)机器人任务框架
@description
    ## Logs
    2020-11-26
    对于一些循环任务, 如扫描二维码, 大多数循环是没有识别结果的, 如果要求必须有返回值,
    会导致串口发送大量的 <ERR>, 这是不太合理的, 因此增加第二种选择, 即当 Task 出错时
    不需要通过串口告知通信对象, 则返回None或返回的label为空字符串(main.py已实现), 主
    程序获取到此类返回值则不向串口发送消息. 
    
    ## Basic Concepts
    目前机器人主程序设计为:
        1. 接收MCU的指令(DataFrame)
        1. 根据指令切换任务
        1. 执行任务, 并获得任务的执行结果
        1. 将结果发送给MCU(DataFrame)
        
    其中可将任务分为两类:
        - Action: 切换到该任务后, 只在本次循环中执行一次并返回结果, 如果后续循环中没有切换到其他任务, 不重复执行该任务, 也没有返回结果
        - Loop: 切换到该任务后, 将在本次和后续循环中重复执行并返回结果, 直到切换到其他任务
    
    所以, 为了便于扩展任务的规模, balabala, 将任务和任务切换的功能抽象出来, 制成框架. 
    使用框架编程可能会感到受到一些限制, 但是习惯之后应该会觉得还是要方便一些, 如果没有
    感到问题变简单的话, 那就是我写得太渣了, 拖累了你们的效率, 轻锤.
    
    所有任务的返回值均为DataFrame格式. 由于DataFrame类型在外部的communication模块中, 为了减少耦合性,
    这里不导入communication模块, 而是使用接口(也可以叫duck typing?), 要求每个任务的返回值都是
    label, values 的元组, 若没有数据可返回, 则返回空字符串和空数组 ('', []).
    
    
    ## TODO
    可以观察到, 主程序 接收->处理->响应 的运行模式与HTTP请求的模式几乎一模一样, 经过分析
    之后我发现貌似可以防Flask的方式, 通过 app.route(cmd, task_type)的方式注册任务. 这种
    做法形式优雅, 使用简单, 熟悉Falsk的话可以很快上手, 但是框架本身可能变复杂, 写起来(应该)
    也会更久. 考虑之后, 还是决定, 不采用Flask装饰器的模式, 而是显式地定义路由字典, 这样
    框架的复杂度降低了, 应用的复杂度上升了, 但是整体来看, 理解整个项目的难度降低了.但最后
    有时间的话, 还是要重构成装饰器版本的, 以享受应用上的诸多好处.
    
    
    ## How to Use
    # implement task.work function
    # make task_list
    # create task_manager and pass it to controller
    
@author Stone at BlueNet, GDOU
@date Nov. 2020
"""
import logging
from parameter import DefaultParameters
from parameter import Parameters


logger = logging.getLogger(__name__)


# TODO use enumerate type
TASK_ACTION = 0
TASK_LOOP = 1



class Task:
    """
    use this class in two way (similar to multiprocessing.Process)
    
    ## way 1, use by inherit
    class VisionTask(Task):
        def work(self, arg):
            label = do_one(arg)
            values = do_two(temp)
            
            return label, values
    
    task = VisionTask()
    
    ## way 2, use by pass function in
    def func(arg):
        temp = do_one(arg)
        result = do_two(temp)
        
        return label, values
    
    task = Task(func=func, args=(arg,))
    """
    def __init__(self, task_type=TASK_ACTION, func=None, args=None, kwargs=None):
        """
        :task_type: 任务类型, TASK_ACTION or TASK_LOOP
        :param func: 要执行的任务函数
        :param *args: 传给func的位置参数
        :param **kwargs: 传给func的关键字参数
        """
        self._task_type = task_type
        self._func = func
        
        if args is None:
            self._args = tuple()
        else:
            self._args = args
        
        if kwargs is None:
            self._kwargs = dict()
        else:
            self._kwargs = kwargs

    def work(self, *args, **kwargs):
        result = self._func(*self._args, **self._kwargs)
        
        return result


class TaskManager:
    def __init__(self, task_list):
        """
        :param task_list<dict>: 以命令(str)为键, Task实例为值的字典
        """
        self._task_list = task_list
        self._current_task = Task(func=lambda: ('', []))
        self._has_executed = False  # 任务是否已执行过
    
    def switch(self, cmd:str):
        """
        根据接收到的 cmd 切换任务. 切换成功返回True, 失败(找不到)返回False.
        :param cmd: task_list 里包含的指令.
        """
        ret = False
        try:
            self._current_task = self._task_list[cmd]
            self._has_executed = False  # 必须放在上一步, 索引字典之后
            ret = True
        except KeyError:
            ret = False
        
        logger.debug('task switch to -> cmd: {}, ret: {}'.format(cmd, ret))
    
    def execute(self):
        """执行当前的任务"""
        if not self._has_executed:
            self._has_executed = True
        elif self._current_task._task_type == TASK_ACTION:
            # Action 类任务执行过一次后便不再执行
            return '', []
            
        result = self._current_task.work()
        
        return result


if __name__ == '__main__':
    """以下既是测试, 也是用法示例"""
    
    # test actions besides args and kwargs
    cmd1 = 'CMD1'
    def func1():
        print('cmd1')
        return cmd1, []
    
    cmd2 = 'CMD2'
    def func2(arg):
        print('cmd2 with arg: {}'.format(arg))
        return cmd2, []
    
    cmd3 = 'CMD3'
    def func3(arg, kw):
        print('cmd3 with arg:{} and kw:{}'.format(arg, kw))
        return cmd3, []
        
    cmd_loop = 'CMD_LOOP'
    def test_loop():
        print('looping')
        return cmd_loop, []
    

    task_list = {
        cmd1: Task(func=func1),
        cmd2: Task(func=func2, args=(3,)),
        cmd3: Task(func=func3, args=(4,), kwargs={'kw': 5}),
        cmd_loop: Task(func=test_loop, task_type=TASK_LOOP),
    }
    
    
    tm = TaskManager(task_list)
    
    print("run with initial task")
    tm.execute()
    
    print("switch and run to task of cmd1")
    tm.switch(cmd1)
    print(tm.execute())
    
    print("switch and run to task of cmd3")
    tm.switch(cmd3)
    print(tm.execute())
    
    print("switch and run to task of cmd2")
    tm.switch(cmd2)
    print(tm.execute())
    
    # ========================================
    print()
    print('test loop with cmd1')
    tm.switch(cmd1)
    for i in range(3):
        print('loop ', i)
        print(tm.execute())
    
    print()
    print('test loop with cmd_loop')
    tm.switch(cmd_loop)
    for i in range(3):
        print('loop ', i)
        print(tm.execute())
    
    



